package visual;

import openfl.Assets;
import openfl.display.Sprite;
import openfl.display.Tileset;
import openfl.display.Tilemap;
import openfl.display.Tile;
import openfl.geom.Rectangle;
import openfl.geom.ColorTransform;

import common.Constants.*;
import common.GridImpl;
import common.cell.Cell;
import common.cell.Empty;

typedef Mutation = {
    x: Int,
    y: Int,
    z: Int,
    to: Cell,
    triggerMoved: Bool,
}

class Grid extends Sprite implements GridImpl {
    private static var TILE_WIDTH = 8;
    private static var TILE_HEIGHT = 12;

    private var tileset: Tileset;
    private var tilemap: Array<Tilemap> = [];
    public var tilearr: Array<Array<Cell>> = [];
    private var charset: Array<String>;

    private var doBatch = false;
    private var batchMutations: Array<Mutation> = [];

    public function new() {
        super();

        // Contruct the tileset.

        charset = "
 ☺☻♥♦♣♠•◘○◙♂♀♪♫☼
►◄↕‼¶§▬↨↑↓→←∟↔▲▼
 !\"#$%&'()*+,-./
0123456789:;<=>?
@ABCDEFGHIJKLMNO
PQRSTUVWXYZ[\\]^_
`abcdefghijklmno
pqrstuvwxyz{|}~⌂
ÇüéâäàåçêëèïîìÄÅ
ÉæÆôöòûùÿÖÜ¢£¥₧ƒ
áíóúñÑªº¿⌐¬½¼¡«»
░▒▓│┤╡╢╖╕╣║╗╝╜╛┐
└┴┬├─┼╞╟╚╔╩╦╠═╬╧
╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀
αßΓπΣσµτΦΘΩδ∞φε∩
≡±≥≤⌠⌡÷≈°∙·√ⁿ²■�"
        .split("").filter(function(char) {
            // Skip over \n
            return char != "\n";
        });

        tileset = new Tileset(Assets.getBitmapData("assets/dwarf.png"));

        var i = 0;
        for (char in charset) {
            // The dwarf.png file is made up of 8x12 characters, with a 1px
            // horizontal space between each (plus one at the start).
            tileset.addRect(new Rectangle(1 + i * 9, 0, TILE_WIDTH, TILE_HEIGHT)); // x, y, w, h

            i++;
        }

        newEmptyLayer(LAYER_MAP);
        newEmptyLayer(LAYER_ENTITIES);
        newEmptyLayer(LAYER_GUI);
    }

    public function batch() {
        doBatch = true;
    }

    public function execBatch() {
        doBatch = false;

        for (mutation in batchMutations) {
            setCell(mutation.x, mutation.y, mutation.z, mutation.to, mutation.triggerMoved);
        }

        batchMutations = [];
    }

    public function newEmptyLayer(z: Int) {
        // Initialize the tilemap
        tilemap[z] = new Tilemap(GRID_WIDTH * TILE_WIDTH, GRID_HEIGHT * TILE_HEIGHT, tileset, false);
        tilemap[z].scaleX = 2;
        tilemap[z].scaleY = 2;

        addChild(tilemap[z]);

        // Fill the new layer with Empty cells.
        tilearr[z] = [];

        for (y in 0...GRID_HEIGHT) {
            for (x in 0...GRID_WIDTH) {
                var e = new Empty(this, x, y, z);
                tilearr[z].push(e);
                tilemap[z].addTileAt(tileFromCell(x, y, e), xyIndex(x, y));
            }
        }
    }

    function xyIndex(x: Int, y: Int): Int {
        if (x >= GRID_WIDTH || y >= GRID_HEIGHT) throw 'Out of bounds: ($x, $y)';

        return y * GRID_WIDTH + x;
    }

    function tileFromCell(x: Int, y: Int, cell: Cell): Tile {
        var tile = new Tile(charset.indexOf(cell.char), x * TILE_WIDTH, y * TILE_HEIGHT);

        // Colors
        tile.colorTransform = switch cell.color {
            case 0: new ColorTransform(0.76, 0.21, 0.13); // red
            case 1: new ColorTransform(0.14, 0.73, 0.14); // green
            case 2: new ColorTransform(0.67, 0.67, 0.15); // yellow
            case 3: new ColorTransform(0,    0.73, 0);    // blue
            case 4: new ColorTransform(0.82, 0.22, 0.82); // magenta
            case 5: new ColorTransform(0,    0.73, 0.73); // cyan
            default: new ColorTransform(); // no change
        }

        return tile;
    }

    public function setCell(x: Int, y: Int, z: Int, to: Cell, triggerMoved = true) {
        if (doBatch) {
            batchMutations.push({ x: x, y: y, z: z, to: to, triggerMoved: triggerMoved });
            return;
        }

        var index = xyIndex(x, y);

        tilemap[z].removeTileAt(index);
        tilearr[z][index].destroyed();

        tilemap[z].addTileAt(tileFromCell(x, y, to), index);
        tilearr[z][index] = to;

        to.x = x;
        to.y = y;
        to.z = z;
        if (triggerMoved) to.moved();
    }

    public function getCell(x: Int, y: Int, z: Int): Cell {
        return tilearr[z][xyIndex(x, y)];
    }
}
